import time, tempfile, http.client, threading, logging, os
import urllib.parse
from jinja2 import Template
import docker
from localsvc.utils import config

CONF_TPL = """server {
    listen {{ port }};
    listen [::]:{{ port }};

    server_name {{ svc_name }};

    location / {
        proxy_pass {{ proxy_pass }};
        {% if ssl_server_name_on %}proxy_ssl_server_name on;{% endif %}
        proxy_http_version 1.1;
        proxy_set_header Host $proxy_host;
    }
}

"""
__template = Template(CONF_TPL)

def ngx_conf_generate(ngx_listening_port: int, svc_name: str, proxy_pass: str, ssl_server_name_on = True) -> str:
    '''Render template CONF_TPL
    @param server_name: nginx config: server_name
    @param proxy_pass: nginx config: location.proxy_pass
    @return nginx conf 'server' section
    '''
    conf = __template.render(
        port = ngx_listening_port,
        svc_name = svc_name,
        proxy_pass = proxy_pass, 
        ssl_server_name_on = ssl_server_name_on
    )
    return conf

class DockerLogReader:

    def __init__(self, container):
        self._st = container.logs(stream=True)
        self._lines = []
        self._thread = threading.Thread(target=self._read)
        self._thread.start()
        self._ix = 0

    def _read(self):
        for ele in self._st:
            self._lines.append(str(ele, 'utf8'))

    def get(self):
        ret = self._lines[self._ix:]
        self._ix += len(ret)
        return ret

    def close(self):
        self._st.close()


class DockerNginxServer:

    '''
    Nginx server instance, docker implementation
    '''

    _container_name: str # container name
    __logger: logging.Logger

    def __init__(self, port):
        self.__logger = logging.getLogger(__name__)
        self._container_name = f'localsvc-nginx-{int(time.time_ns() / 1000_000_000)}'
        # self._container_name = f'localsvc-nginx'
        self._port = port

    @property
    def container_name(self):
        return self._container_name

    def _get_docker_client(self) -> docker.DockerClient:
        return docker.from_env()

    def __is_nginx_running(self):
        conn = http.client.HTTPConnection('localhost', self._port)
        try:
            conn.request('GET', '/')
            r = conn.getresponse()
            return True
        except:
            return False
        finally:
            conn.close()

    def start(self, conf_file):
        '''start nginx server
        '''
        mounts = [f'{conf_file}:/etc/nginx/conf.d/localsvc.conf']
        nginx_conf_dir = config.find_file('scripts/')
        if nginx_conf_dir is not None:
            files = os.listdir(nginx_conf_dir)
            for fname in files:
                if not fname.startswith('test-') and fname.endswith('.conf'):
                    config_name = os.path.basename(fname)
                    mounts.append(f'{os.path.join(nginx_conf_dir, fname)}:/etc/nginx/conf.d/{config_name}')

        client = self._get_docker_client()
        try:
            container = client.containers.run(
                'nginx:latest', 
                name=self._container_name,
                detach=True, 
                auto_remove=True, 
                volumes=mounts, 
                ports={self._port: self._port}
            )
            container_log = DockerLogReader(container)
            try:
                for i in range(0, 30):
                    time.sleep(1)
                    if self.__is_nginx_running():
                        return
                    # still not running, check container if container terminated
                    self.__logger.info(''.join(container_log.get()))
                    client.containers.get(self._container_name)
            except docker.errors.NotFound:
                self.__logger.error('Container terminated')
                raise RuntimeError('nginx container terminated')
            finally:
                container_log.close()
        finally:
            client.close()

    def stop(self):
        '''Stop nginx server
        '''
        if self._container_name is None:
            return
        client = self._get_docker_client()
        try:
            container = client.containers.get(self._container_name)
            if container is not None:
                container.stop()
            self._container_name = None
        finally:
            client.close()

    def reload(self):
        '''nginx -s reload
        '''
        if self._container_name is None:
            return
        client = self._get_docker_client()
        try:
            client.containers.get(self._container_name).exec_run('nginx -s reload', detach=True)
        finally:
            client.close()

class NginxProxy:
    '''Proxy server implemented by docker nginx
    '''

    _nginx: DockerNginxServer
    _nginx_started: bool
    _conf_file: str

    def __init__(self, port):
        '''
        @param port nginx listening port
        '''
        self._port = port
        # dict[target_url, (service_name, host, port)]
        self._proxy_by_url = {}
        self._conf_file = tempfile.mktemp()
        self._nginx = DockerNginxServer(port)
        self._nginx_started = False

    def _create_proxy_conf(self, ngx_server_name, ngx_proxy_pass) -> str:
        '''生成目标地址的代理配置
        @return nginx conf snippet
        '''
        u = urllib.parse.urlparse(ngx_proxy_pass)
        return ngx_conf_generate(self._port, ngx_server_name, ngx_proxy_pass, u.scheme == 'https')

    def _create_full_conf(self) -> str:
        '''遍历所有需要代理的地址，计算结果，返回完整的nginx配置文件
        '''
        lst = []
        for target_url, t in self._proxy_by_url.items():
            (service_name, server_name, port) = t
            conf = self._create_proxy_conf(server_name, target_url)
            lst.append(conf)
        return ''.join(lst)

    def proxy(self, service_name: str, target_url: str) -> tuple[str, int]:
        '''开始代理某个服务
        @return proxied server's endpoint: host, port
        '''
        if target_url in self._proxy_by_url:
            r = self._proxy_by_url[target_url][1]
            return r[1], r[2]
        server_name = f'{service_name}.{self._nginx.container_name}.localhost' # nginx conf: server_name
        self._proxy_by_url[target_url] = (service_name, server_name, self._port)
        # reload if nginx is running
        if self._nginx_started:
            content = self._create_full_conf()
            with open(self._conf_file, 'w', encoding='utf-8') as fd:
                fd.write(content)
            self._nginx.reload()
        return server_name, self._port

    def start(self):
        '''启动代理服务
        '''
        if self._nginx_started:
            return
        conf = self._create_full_conf()
        with open(self._conf_file, 'w', encoding='utf-8') as fd:
            fd.write(conf)
        self._nginx.start(self._conf_file)
        self._nginx_started = True

    def stop(self):
        '''停止代理服务
        '''
        if self._nginx_started:
            self._nginx.stop()
        self._nginx_started = False

    def catlog(self):
        '''显示代理服务器日志
        '''
        # TODO
        pass